/*
 * Unidata Platform
 * Copyright (c) 2013-2020, UNIDATA LLC, All rights reserved.
 *
 * Commercial License
 * This version of Unidata Platform is licensed commercially and is the appropriate option for the vast majority of use cases.
 *
 * Please see the Unidata Licensing page at: https://unidata-platform.com/license/
 * For clarification or additional options, please contact: info@unidata-platform.com
 * -------
 * Disclaimer:
 * -------
 * THIS SOFTWARE IS DISTRIBUTED "AS-IS" WITHOUT ANY WARRANTIES, CONDITIONS AND
 * REPRESENTATIONS WHETHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE
 * IMPLIED WARRANTIES AND CONDITIONS OF MERCHANTABILITY, MERCHANTABLE QUALITY,
 * FITNESS FOR A PARTICULAR PURPOSE, DURABILITY, NON-INFRINGEMENT, PERFORMANCE AND
 * THOSE ARISING BY STATUTE OR FROM CUSTOM OR USAGE OF TRADE OR COURSE OF DEALING.
 */
package org.unidata.mdm.dq.core.context;

import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;

import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.unidata.mdm.core.type.model.CustomPropertyElement;
import org.unidata.mdm.dq.core.type.cleanse.CleanseFunctionInputParam;

/**
 * Cleanse function execution context.
 * Describes the environment, within a cleanse function executes.
 * Input parameters can be extracted using method {@link #getInputParam(String)}.
 *
 * @author ilya.bykov
 */
public class CleanseFunctionContext {
    /**
     * Executing CF name.
     */
    private final String functionName;
    /**
     * Executing rule name.
     */
    private final String ruleName;
    /**
     * The currently selected locale or system default.
     */
    private final Locale currentLocale;
    /**
     * Custom properties, set up for this CF.
     */
    private final Collection<CustomPropertyElement> customProperties;
    /**
     * Input by port name.
     */
    private final Map<String, CleanseFunctionInputParam> input = new HashMap<>(8);
    /**
     * The payload to save.
     */
    private final Object payload;
    /**
     * Instantiates a new CF context.
     */
    private CleanseFunctionContext(CleanseFunctionContextBuilder b) {
        this.functionName = b.functionName;
        this.ruleName = b.ruleName;
        this.currentLocale = b.currentLocale;
        this.customProperties = b.customProperties;
        this.payload = b.payload;
        if (Objects.nonNull(b.input)) {
            this.input.putAll(b.input);
        }
    }
    /**
     * Gets the name of the rule, being executed.
     * @return the ruleName
     */
    public String getRuleName() {
        return ruleName;
    }
    /**
     * Gets the cleanse function name, as defined by the model.
     * @return the functionName
     */
    public String getFunctionName() {
        return functionName;
    }
    /**
     * @return the currentLocale
     */
    public Locale getCurrentLocale() {
        return currentLocale;
    }
    /**
     * @return the customProperties
     */
    public Collection<CustomPropertyElement> getCustomProperties() {
        return Objects.isNull(customProperties) ? Collections.emptyList() : customProperties;
    }
    /**
     * @return the payload
     */
    @SuppressWarnings("unchecked")
    public<T> T getPayload() {
        return (T) payload;
    }
    /**
     * Returns true, if some payload is set.
     * @return true, if set
     */
    public boolean hasPayload() {
        return Objects.nonNull(payload);
    }
    /**
     * Gets input param by port name.
     * @param portName the port name
     * @return param or null
     */
    public CleanseFunctionInputParam getInputParam(String portName) {
        return input.get(portName);
    }
    /**
     * Returns input params as a collection.
     * @return input params as a collection
     */
    public Collection<CleanseFunctionInputParam> getInputParams() {
        return MapUtils.isNotEmpty(input) ? input.values() : Collections.emptyList();
    }
    /**
     * Returns true, if this result has some input params set.
     * @return true, if this result has some input params set.
     */
    public boolean hasInputParams() {
        return MapUtils.isNotEmpty(input) ;
    }
    /**
     * Input port names.
     * @return names
     */
    public Collection<String> getInputPorts() {
        return input.keySet();
    }
    /**
     * @param param the action to set
     */
    public void input(CleanseFunctionInputParam param) {
        input.put(param.getPortName(), param);
    }
    /**
     * @param params the action to set
     */
    public void input(CleanseFunctionInputParam... params) {
        for (int i = 0; params != null && i < params.length; i++) {
            input(params[i]);
        }
    }
    /**
     * @param params the action to set
     */
    public void input(Collection<CleanseFunctionInputParam> params) {
        for (CleanseFunctionInputParam param : params) {
            input(param);
        }
    }
    /**
     * Copy builder without params.
     * @param other the context
     * @return builder
     */
    public static CleanseFunctionContextBuilder builder(CleanseFunctionContext other) {
        return new CleanseFunctionContextBuilder(other);
    }
    /**
     * The usual builder.
     * @return builder
     */
    public static CleanseFunctionContextBuilder builder() {
        return new CleanseFunctionContextBuilder();
    }
    /**
     * @author Mikhail Mikhailov
     * The ususal builder class.
     */
    public static class CleanseFunctionContextBuilder {
        /**
         * Executing CF name.
         */
        private String functionName;
        /**
         * Executing rule name.
         */
        private String ruleName;
        /**
         * The currently selected locale or system default.
         */
        private Locale currentLocale;
        /**
         * Custom properties, set up for this CF.
         */
        private Collection<CustomPropertyElement> customProperties;
        /**
         * Cleanse function input.
         */
        private Map<String, CleanseFunctionInputParam> input;
        /**
         * The payload to save.
         */
        private Object payload;
        /**
         * Constructor.
         */
        private CleanseFunctionContextBuilder() {
            super();
        }
        /**
         * Constructor.
         */
        private CleanseFunctionContextBuilder(CleanseFunctionContext other) {
            this.functionName = other.functionName;
            this.ruleName = other.ruleName;
            this.currentLocale = other.currentLocale;
            this.customProperties = other.customProperties;
            this.payload = other.payload;
        }
        /**
         * Required.
         * @param ruleName the ruleName to set
         * @return self
         */
        public CleanseFunctionContextBuilder ruleName(String ruleName) {
            this.ruleName = ruleName;
            return this;
        }
        /**
         * Required.
         * @param functionName the functionName to set
         * @return self
         */
        public CleanseFunctionContextBuilder functionName(String functionName) {
            this.functionName = functionName;
            return this;
        }
        /**
         * Sets current locale.
         * @param currentLocale the locale to set
         * @return self
         */
        public CleanseFunctionContextBuilder currentLocale(Locale currentLocale) {
            this.currentLocale = currentLocale;
            return this;
        }
        /**
         * Sets custom properties.
         * @param customProperties the custom properties to set
         * @return self
         */
        public CleanseFunctionContextBuilder customProperties(Collection<CustomPropertyElement> customProperties) {
            this.customProperties = customProperties;
            return this;
        }
        /**
         * @param params the action to set
         * @return self
         */
        public CleanseFunctionContextBuilder input(CleanseFunctionInputParam... params) {

            if (ArrayUtils.isNotEmpty(params)) {
                return input(Arrays.asList(params));
            }

            return this;
        }
        /**
         * @param params the action to set
         * @return self
         */
        public CleanseFunctionContextBuilder input(Collection<CleanseFunctionInputParam> params) {

            if (CollectionUtils.isNotEmpty(params)) {
                for (CleanseFunctionInputParam param : params) {

                    if (Objects.isNull(input)) {
                        input = new HashMap<>();
                    }

                    input.put(param.getPortName(), param);
                }
            }

            return this;
        }
        /**
         * Sets arbitrary payload.
         * @param payload the payload
         * @return self
         */
        public<T> CleanseFunctionContextBuilder payload(T payload) {
            this.payload = payload;
            return this;
        }

        /**
         * Build.
         * @return ctx
         */
        public CleanseFunctionContext build() {
            return new CleanseFunctionContext(this);
        }
    }
}
